package org.meb.conquest.db.dao;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.SingularAttribute;

import org.meb.conquest.db.dao.Query.Mode;
import org.meb.conquest.db.util.PropType;
import org.meb.conquest.db.util.PropUtils;

public class JpaDao<T> {

	private EntityManager em;

	public JpaDao(EntityManager em) {
		this.em = em;
	}

	protected EntityManager getEntityManager() {
		return em;
	}

	protected CriteriaQuery<T> createCriteriaQuery(Query<T> query) {

		T example = query.getExample();
		@SuppressWarnings("unchecked")
		Class<T> exampleClazz = (Class<T>) example.getClass();

		ManagedType<T> managedType = em.getMetamodel().managedType(exampleClazz);

		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<T> cq = cb.createQuery(exampleClazz);
		Root<T> root = cq.from(exampleClazz);
		cq.select(root);

		List<Predicate> predicates = new ArrayList<Predicate>(3);

		Field[] fields = PropUtils.getFields(exampleClazz, PropType.JPA);
		for (Field field : fields) {
			String fieldName = field.getName();
			Class<?> fieldType = field.getType();
			Object fieldValue = PropUtils.getProperty(example, fieldName);
			if (fieldValue == null) {
				continue;
			}

			Predicate predicate;
			if (fieldType == String.class) {
				SingularAttribute<? super T, String> attribute = managedType.getSingularAttribute(
						fieldName, String.class);
				predicate = cb.like(cb.lower(root.get(attribute)), (String) fieldValue);
			} else {
				SingularAttribute<? super T, ?> attribute = managedType.getSingularAttribute(
						fieldName, fieldType);
				predicate = cb.equal(root.get(attribute), fieldValue);
			}
			predicates.add(predicate);
		}

		if (predicates.size() > 0) {
			if (query.getMode() == Mode.AND) {
				cq.where(predicates.toArray(new Predicate[predicates.size()]));
			} else if (query.getMode() == Mode.OR) {
				cq.where(cb.or(predicates.toArray(new Predicate[predicates.size()])));
			}
		}

		return cq;
	}

	protected TypedQuery<T> createQuery(Query<T> query) {
		return em.createQuery(createCriteriaQuery(query));
	}

	public List<T> find(T example) {
		return createQuery(new Query<T>(example)).getResultList();
	}

	public T findUnique(T example) {
		T sb;

		try {
			sb = createQuery(new Query<T>(example)).getSingleResult();
		} catch (NoResultException e) {
			sb = null;
		}
		return sb;
	}

	public List<T> find(Query<T> query) {
		return createQuery(query).getResultList();
	}

	public T findUnique(Query<T> query) {
		T sb;

		try {
			sb = createQuery(query).getSingleResult();
		} catch (NoResultException e) {
			sb = null;
		}
		return sb;
	}
}
